package com.sowka.miko.weather.provider.internal;

import java.net.MalformedURLException;
import java.net.URL;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.HashMap;
import java.util.Map;

import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.DefaultHandler;
import org.xml.sax.helpers.XMLReaderFactory;

import com.sowka.miko.weather.model.Conditions;
import com.sowka.miko.weather.model.Forecast;
import com.sowka.miko.weather.provider.AbstractWeatherProvider;

public class GoogleWeatherProvider extends AbstractWeatherProvider {

	private static final String BASE_URL = "http://www.google.com";
	private static final String WEATHER_API_URL = "/ig/api?weather=";
	
	/**
	 * Crude parser for the google weather api that's undocumented but assumed
	 * to be fairly stable. Crude since it assumes that all conditions information
	 * is stored in the xml elements single 'data' attribute, etc.
	 * 
	 * For forecast conditions, assuming the default local, Fahrenheit is the unit
	 * foro the low and high temperatures.
	 */
	private class ResponseHandler extends DefaultHandler {

		private boolean isCurrentCond;
		private Conditions conditions;
		
		private Map<String, Integer> daysOfWeek = new HashMap<String, Integer>();
		{
			daysOfWeek.put("Mon", Calendar.MONDAY);
			daysOfWeek.put("Tue", Calendar.TUESDAY);
			daysOfWeek.put("Wed", Calendar.WEDNESDAY);
			daysOfWeek.put("Thu", Calendar.THURSDAY);
			daysOfWeek.put("Fri", Calendar.FRIDAY);
			daysOfWeek.put("Sat", Calendar.SATURDAY);
			daysOfWeek.put("Sun", Calendar.SUNDAY);
		}
		
		@Override
		public void startDocument() throws SAXException {
			forecast = new Forecast();
			conditions = new Conditions();
		}

		@Override
		public void startElement(String uri, String localName, String qName,
				Attributes attributes) throws SAXException {
			if (localName.equals("current_conditions")) {
				isCurrentCond = true;
			} else if (localName.equals("forecast_conditions")) {
				// if it was the current condition, and reset the flag; subsequent
				// conditions are assumed to be forecast conditions
				if (isCurrentCond) {
					forecast.setCurrent(conditions);
					isCurrentCond = false;
				}
			} else if (localName.equals("forecast_date")) { 
				SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
				try {
					conditions.setDate(dateFormat.parse(attributes.getValue(0)));
				} catch (ParseException e) {
					// TODO log the failed conditions date parsing
					e.printStackTrace();
				}
			} else if (localName.equals("day_of_week")) {
				Integer forecastDay = daysOfWeek.get(attributes.getValue(0));
				if (forecastDay == null) {
					throw new SAXException("Unrecognized day of the week" + attributes.getValue(0));
				}
				Calendar cal = Calendar.getInstance();
				cal.setTime(forecast.getCurrent().getDate());
				int currentDay = cal.get(Calendar.DAY_OF_WEEK);
				int daysDelta = forecastDay - currentDay;
				if (daysDelta < 0) daysDelta += 7;
				cal.add(Calendar.DATE, daysDelta);
				conditions.setDate(cal.getTime());
			} else if (localName.equals("condition")) {
				conditions.setDesc(attributes.getValue(0));
			} else if (localName.equals("humidity")) {
				// assuming that returned value is of "Humidity: 69%" format
				String humidity = attributes.getValue(0).split(" |%")[1];
				conditions.setHumidity(Float.parseFloat(humidity));
			} else if (localName.equals("icon")) {
				try {
					conditions.setIcon(new URL(BASE_URL + attributes.getValue(0)));
				} catch (MalformedURLException e) {
					// This really shouldn't happen...
				}
			} else if (localName.equals("temp_c")) {
				conditions.setTempC(Float.parseFloat(attributes.getValue(0)));
			} else if (localName.equals("temp_f")) {
				conditions.setTempF(Float.parseFloat(attributes.getValue(0)));
			} else if (localName.equals("low")) {
				conditions.setLowTempF(Float.parseFloat(attributes.getValue(0)));
			} else if (localName.equals("high")) {
				conditions.setTempF(Float.parseFloat(attributes.getValue(0)));
			} else if (localName.equals("wind_condition")) {
				conditions.setWind(attributes.getValue(0));
			}
			
		}

		@Override
		public void endElement(String uri, String localName, String qName)
				throws SAXException {
			if (localName.equals("forecast_conditions")) {
				forecast.addForecast(conditions);
				conditions = new Conditions();
			}
		}
	}
	
	@Override
	public Forecast fetchForecast() {
		ResponseHandler handler = new ResponseHandler();
		
		try {
			XMLReader reader = XMLReaderFactory.createXMLReader();
			reader.setContentHandler(handler);
			reader.setErrorHandler(handler);
			
			reader.parse(new InputSource(
					new URL(BASE_URL + WEATHER_API_URL + "Ottawa,ON").openStream()));
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		return forecast;
	}

}
